package com.thoughtworks.bytecode2class.constantpool;

import com.thoughtworks.bytecode2class.ClassReader;

import java.util.*;

public class ConstantPool {

    private static final int CONSTANT_Utf8 = 1;
    private static final int CONSTANT_Integer = 3;
    private static final int CONSTANT_Float = 4;
    private static final int CONSTANT_Long = 5;
    private static final int CONSTANT_Double = 6;
    private static final int CONSTANT_Class = 7;
    private static final int CONSTANT_String = 8;
    private static final int CONSTANT_FieldRef = 9;
    private static final int CONSTANT_MethodRef = 10;
    private static final int CONSTANT_InterfaceMethodRef = 11;
    private static final int CONSTANT_NameAndType = 12;

    private final Map<Integer, Constant> poolMap;

    private int currentCount = 1;

    public ConstantPool() {
        poolMap = new TreeMap<Integer, Constant>();
    }

    public int getCurrentCount() {
        return currentCount;
    }

    public Constant get(int index) {
        return poolMap.get(index);
    }

    public StringConstant getStringConstant(int index) {
        return (StringConstant) get(index);
    }

    public MethodRefConstant getMethodRefConstant(int index) {
        return (MethodRefConstant) get(index);
    }

    public FieldRefConstant getFieldConstant(int index) {
        return (FieldRefConstant) get(index);
    }

    public ClassConstant getClassConstant(int index) {
        return (ClassConstant) get(index);
    }

    public Utf8Constant getUtf8Constant(int index) {
        return (Utf8Constant) get(index);
    }

    public NameAndTypeConstant getNameAndTypeConstant(int index) {
        return (NameAndTypeConstant) get(index);
    }

    public Collection<Constant> getConstants() {

        List<Constant> sortedConstants = new ArrayList<Constant>(poolMap.keySet().size());
        for (Integer integer : poolMap.keySet()) {
            sortedConstants.add(poolMap.get(integer));
        }
        return sortedConstants;
    }

    public LongConstant getLongConstant(int index) {
        return (LongConstant) get(index);
    }

    public IntegerConstant getIntegerConstant(int index) {
        return (IntegerConstant) get(index);
    }

    public FloatConstant getFloatConstant(int count) {
        return (FloatConstant) get(count);
    }

    public DoubleConstant getDoubleConstant(int count) {
        return (DoubleConstant) get(count);
    }

    public void create(ClassReader classReader, int type) {
        Constant constant = doCreate(classReader, type);
        add(constant);
    }

    private void add(Constant constant) {
        constant.setCount(currentCount++);
        if (constant instanceof DoubleConstant ||
                constant instanceof LongConstant) {
            currentCount++;
        }
        poolMap.put(constant.getCount(), constant);
    }

    private Constant doCreate(ClassReader classReader, int type) {
        if (type == CONSTANT_Utf8) {
            return new Utf8Constant(classReader);
        } else if (type == CONSTANT_Integer) {
            return new IntegerConstant(classReader);
        } else if (type == CONSTANT_Float) {
            return new FloatConstant(classReader);
        } else if (type == CONSTANT_Long) {
            return new LongConstant(classReader);
        } else if (type == CONSTANT_Double) {
            return new DoubleConstant(classReader);
        } else if (type == CONSTANT_Class) {
            return new ClassConstant(classReader);
        } else if (type == CONSTANT_String) {
            return new StringConstant(classReader);
        } else if (type == CONSTANT_FieldRef) {
            return new FieldRefConstant(classReader);
        } else if (type == CONSTANT_MethodRef) {
            return new MethodRefConstant(classReader);
        } else if (type == CONSTANT_InterfaceMethodRef) {
            return new InterfaceMethodRefConstant(classReader);
        } else if (type == CONSTANT_NameAndType) {
            return new NameAndTypeConstant(classReader);
        }
        throw new RuntimeException("Unknown type: " + type);
    }
}
